﻿using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using Engine.Sgml.Matching;
using Engine.Dtd.Schema;
using System.Collections.Generic;

namespace Engine.Sgml.Merging {
    /// <summary>
    /// Represents an inconsistent block of elements.
    /// </summary>
    [DebuggerDisplay("{StartIndex} - {EndIndex}")]
    internal sealed class InconsistentBlock : Collection<SElement> {
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private SElement initialElement;

        /// <summary>
        /// Initialize new instance of the <c>InconsistentBlock</c> object.
        /// </summary>
        public InconsistentBlock(SElement ancestor)
            : base() {
            Debug.Assert(ancestor != null);
            Ancestor = ancestor;
            }

        /// <summary>
        /// Restores an original position of block elements.
        /// </summary>
        public void Restore() {
            // If a sequence of nodes in wich this block included was
            // changed before
            if (Count == 0)
                return;
            int index = StartIndex;
            for (int i = 0; i < Count; i++) {
                SElement element = Items[i];
                if (object.ReferenceEquals(element.Parent, Ancestor)) {
                    Ancestor.Nodes.Move(element.OrderIndex, index++);
                    }
                else {
                    element.Remove();
                    Ancestor.Nodes.Insert(element, index++);
                    element.Parent = Ancestor;
                    }
                }
            }

        internal void Relocate() {
            for (int i = 0; i < Count; i++) {
                SElement element = Items[i];
                // If an ancestor of the current block is removed previously
                // use a parent of the current element in the block
                SElement ancestor = element.Parent;
                int position = element.OrderIndex;
                for (int k = element.Nodes.Count - 1; k >= 0; k--) {
                    SElement node = element.Nodes[k];
                    if (ElementMatcher.IsCunstructedElement(node)) {
                        node.Parent.Nodes.RemoveAt(node.OrderIndex);
                        node.Parent = null;
                        }
                    else
                        element.AddAfterSelf(node);
                    }
                }
            }

        internal void RecalculateStartIndex() {
            if (Count == 0)
                return;
            initialElement = Items[0].PreviousNode;
            }

        internal void RemoveFromAncestor() {
            for (int j = 0; j < Count; j++) {
                SElement node = Items[j];
                Debug.Assert(node.Parent != null);
                node.Parent.Nodes.RemoveAt(node.OrderIndex);
                node.Parent = null;
                }
            }

        /// <summary>
        /// Inserts an element into this block at the specified index.
        /// </summary>
        /// <param name="index">A zero based index.</param>
        /// <param name="item">A SElement object to insert. The value can not be null.</param>
        protected override void InsertItem(int index, SElement item) {
            Guard.ArgumentNotNull(item, "item");
            Debug.Assert(item.Parent == Ancestor, "The inserted item doesn't belong to the block.");
            Debug.Assert(CanAddElement(item));
            if (Count == 0)
                initialElement = item.PreviousNode;
            if (item.Parent == Ancestor)
                base.InsertItem(index, item);
            else
                throw new InvalidOperationException("The parent of inconsistent element does not belong to the ancestor.");
            }

        /// <summary>
        /// Indicates whether the specified element can be added into this block.
        /// </summary>
        /// <param name="element">A SElement object to evaluate.</param>
        /// <returns>true if element can be added; otherwise, false.</returns>
        public bool CanAddElement(SElement element) {
            if (element == null)
                return false;
            if (element.DefinedInScheme && element.Parent == Ancestor) {
                if (Count > 0) {
                    if (LastElement.OrderIndex == element.OrderIndex - 1)
                        return true;
                    SElement last = LastElement;
                    if (last.OrderIndex < element.OrderIndex) {
                        SElement previous = element.PreviousNode;
                        do {
                            if (previous.NodeType != SgmlNodeType.Comment && !ElementMatcher.IsCunstructedElement(previous))
                                return false;
                            previous = previous.PreviousNode;
                            } while (!object.ReferenceEquals(previous, last));
                        }
                    else
                        return false;
                    }
                return true;
                }
            else
                return false;
            }

        /// <summary>
        /// Gets the ancestor of elements of this block.
        /// </summary>
        internal SElement Ancestor { get; private set; }

        /// <summary>
        /// Gets the starting index of this block.
        /// </summary>
        public int StartIndex {
            get { return Count == 0 ? -1 : initialElement == null ? 0 : initialElement.OrderIndex + 1; }
            }

        /// <summary>
        /// Gets the ending index of this block.
        /// </summary>
        public int EndIndex {
            get { return Count == 0 ? -1 : Items[Count - 1].OrderIndex; }
            }

        /// <summary>
        /// Gets the first element of this block.
        /// </summary>
        public SElement FirstElement {
            get { return Count > 0 ? Items[0] : null; }
            }

        /// <summary>
        /// Gets the last element of this block.
        /// </summary>
        public SElement LastElement {
            get { return Count > 0 ? Items[Count - 1] : null; }
            }

        /// <summary>
        /// Searches the element that is correspond the specified model and returns the zero-based index of 
        /// the first occurrence within this block.
        /// </summary>
        /// <param name="model">The model of element to locate in this block. The value can not be null.</param>
        /// <returns>The zero-based index of the first occurrence of element that is correspond the specified model within the entire block, if found; otherwise, -1.</returns>
        public int IndexOf(IModelGroupsNode model) {
            Guard.ArgumentNotNull(model, "model");
            for (int i = 0; i < Count; i++)
                if (Items[i].DName == model.Name)
                    return i;
            return -1;
            }

        internal void AppendBefore(SElement element) {
            Debug.Assert(Count > 0);
            Debug.Assert(element.Parent != null);
            for (int i = 0; i < Count; i++) {
                Debug.Assert(element != Items[i]);
                element.AddBeforeSelf(Items[i]);
                }
            }

        internal void AppendAfter(SElement element) {
            Debug.Assert(Count > 0);
            Debug.Assert(element.Parent != null);
            for (int i = Count - 1; i >= 0; i--) {
                Debug.Assert(element != Items[i]);
                element.AddAfterSelf(Items[i]);
                }
            }

        internal void InsertAtStart(SElement ancestor) {
            Debug.Assert(ancestor != null);
            Debug.Assert(Count > 0);
            for (int i = Count - 1; i >= 0; i--) {
                Debug.Assert(ancestor != Items[i]);
                ancestor.AddFirst(Items[i]);
                }
            }

        internal void InsertAtEnd(SElement ancestor) {
            Debug.Assert(ancestor != null);
            Debug.Assert(Count > 0);
            for (int i = 0; i < Count; i++) {
                Debug.Assert(ancestor != Items[i]);
                ancestor.Add(Items[i]);
                }
            }
        }
    }
